package com.mjlf.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.CharsetUtil;

import javax.xml.crypto.Data;
import java.util.Date;

/**
 * @ClassName Server
 * @auther mjlf
 * @Date 2019/9/24 15:17
 * @Description websocket 服务端测试
 */
public class Server {

    public static void main(String[] args) {
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup work = new NioEventLoopGroup();

        try{
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boss, work)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    //请求和应答http编码/解密
                                    .addLast("http-codec", new HttpServerCodec())
                                    //将http消息的多个部分组合成一条完整的http消息
                                    .addLast("aggregator", new HttpObjectAggregator(65536))
                                    //用来向客户端发送html5文件
                                    .addLast("http-chunked", new ChunkedWriteHandler())
                                    .addLast("handler", new WebSocketServerHandler());
                        }
                    });

            Channel channel = bootstrap.bind(9090).sync().channel();
            channel.closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }

    static class WebSocketServerHandler extends SimpleChannelInboundHandler {

        private WebSocketServerHandshaker handshaker;

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {

            //传统HTTP接入
            if(msg instanceof FullHttpRequest){
                handlerHttpRequest(ctx, (FullHttpRequest)msg);
            }else if(msg instanceof WebSocketFrame){
                handlerWebSocketFrame(ctx, (WebSocketFrame)msg);
            }
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println(cause.getMessage());
            ctx.close();
        }

        public void handlerHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request){
            if(!request.decoderResult().isSuccess() || !("websocket".equals(request.headers().get("Upgrade")))){
                sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
                return;
            }

            //建立握手响应返回， 本机测试
            WebSocketServerHandshakerFactory wsfactory = new WebSocketServerHandshakerFactory("ws://localhost:9090/websocket", null, false);
            handshaker = wsfactory.newHandshaker(request);
            if(handshaker == null){
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            }else {
                handshaker.handshake(ctx.channel(), request);
            }
        }

        public void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
            if(frame instanceof CloseWebSocketFrame){
                handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain());
                return;
            }

            if(frame instanceof PingWebSocketFrame){
                ctx.channel().write(frame.content().retain());
                return;
            }

            if(!(frame instanceof  TextWebSocketFrame)){
                throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName()));
            }

            String request = ((TextWebSocketFrame)frame).text();

            ctx.channel().write(new TextWebSocketFrame(request + ", 欢迎使用Netty WebSocket 服务， 现在时刻" + (new Date().toString())));
        }

        private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res){
            if(res.status().code() != 200){
                ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
                res.content().writeBytes(buf);
                buf.release();

                HttpUtil.setContentLength(res, res.content().readableBytes());
            }

            ChannelFuture f = ctx.channel().writeAndFlush(res);
            if(!HttpUtil.isKeepAlive(req) || res.status().code() != 200){
                f.addListener(ChannelFutureListener.CLOSE);
            }
        }
    }
}
